package com.rizzo.web.util;

import com.rizzo.back.exception.ApplicationException;
import com.rizzo.back.exception.ApplicationExceptionItem;
import org.springframework.binding.message.MessageBuilder;
import org.springframework.binding.message.MessageContext;
import org.springframework.context.MessageSource;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.execution.RequestContextHolder;

import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import java.text.MessageFormat;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;

/**
 * Factory class for retrieving server-side i18n messages for the Java ES(tm)
 * Monitoring Console within the JSF framework. This class does not override all
 * methods in com.sun.webui.jsf.util.MessageUtil; it merely provides a few
 * commonly used methods for getting resources using the Resources.properties
 * files for this web application. It can also access the messages from the
 * default messages.properties from the active Flow.
 *
 * @author Jan Lievens
 */
public class MessageUtil {

    /**
     * Private constructor because it's a util class.
     */
    public MessageUtil() {
    }

    /**
     * Resolves a messageKey to a full message from the flow messages.properties bundle.
     *
     * @param key The key for which we want to resolve the message.
     * @return The value of the message
     */
    static public String resolveFlowMessage(String key) {
        MessageSourceAccessor messageSourceAccessor = getMessageSourceAccessor();
        return messageSourceAccessor.getMessage(key);
    }

    /**
     * Resolves a messageKey to a full message from the flow messages.properties bundle with args.
     *
     * @param key  The key for which we want to resolve the message.
     * @param args The args to fill out the placeholders of the message.
     * @return The value of the message
     */
    static public String resolveFlowMessage(String key, Object[] args) {
        MessageSourceAccessor messageSourceAccessor = getMessageSourceAccessor();
        return messageSourceAccessor.getMessage(key, args);
    }

    /**
     * Resolves a messageKey to a full message from the flow messages.properties bundle with a default message if not found.
     *
     * @param key            The key for which we want to resolve the message.
     * @param defaultMessage The default message if the key is not found in the bundle.
     * @return The value of the message
     */
    static public String resolveFlowMessage(String key, String defaultMessage) {
        MessageSourceAccessor messageSourceAccessor = getMessageSourceAccessor();
        return messageSourceAccessor.getMessage(key, defaultMessage);
    }

    /**
     * Resolves a messageKey to a full message from the flow messages.properties bundle with a default message if not found and args.
     *
     * @param key            The key for which we want to resolve the message.
     * @param args           The args to fill out the placeholders of the message.
     * @param defaultMessage The default message if the key is not found in the bundle.
     * @return The value of the message.
     */
    static public String resolveFlowMessage(String key, Object[] args,
                                            String defaultMessage) {
        MessageSourceAccessor messageSourceAccessor = getMessageSourceAccessor();
        return messageSourceAccessor.getMessage(key, args, defaultMessage);
    }

    /**
     * Accesses the MessageSourceAccessor from the active flow so we can resolve messages from its bundle.
     *
     * @return The {@link org.springframework.context.support.MessageSourceAccessor} from the RequestContextHolder.
     */
    private static MessageSourceAccessor getMessageSourceAccessor() {
        RequestContext requestContext = RequestContextHolder
                .getRequestContext();
        MessageSource messageSource = requestContext.getActiveFlow()
                .getApplicationContext();
        return new MessageSourceAccessor(messageSource);
    }

    /**
     * Places all the {@link be.ganymed.gouse.exception.ApplicationExceptionItem} onto the
     * {@link org.springframework.binding.message.MessageContext}.
     *
     * @param messageContext The {@link org.springframework.binding.message.MessageContext} on which the messages must be placed.
     * @param e              The {@link be.ganymed.gouse.exception.ApplicationException} with its items.
     */
    public static void populateMessages(MessageContext messageContext, ApplicationException e) {
        for (ApplicationExceptionItem applicationExceptionItem : e.getApplicationExceptionItems())
            messageContext.addMessage(new MessageBuilder().fatal().code(
                    applicationExceptionItem.getMsgKey()).args(applicationExceptionItem.getMsgParams()).build());
    }

    /**
     * Adds an error-level message specified by its key to the {@link org.springframework.binding.message.MessageContext}.
     *
     * @param messageContext The {@link org.springframework.binding.message.MessageContext} to which a resolved message must be added.
     * @param key            The key of the message in the FlowResourceBundle.
     */
    public static void addErrorFlowMessage(MessageContext messageContext, String key) {
        messageContext.addMessage(new MessageBuilder().error().code(
                key).build());
    }

    /**
     * Adds an fatal-level message specified by its key to the {@link org.springframework.binding.message.MessageContext}.
     *
     * @param messageContext The {@link org.springframework.binding.message.MessageContext} to which a resolved message must be added.
     * @param key            The key of the message in the FlowResourceBundle.
     */
    public static void addFatalFlowMessage(MessageContext messageContext, String key) {
        messageContext.addMessage(new MessageBuilder().fatal().code(
                key).build());
    }

    /**
     * Adds an info-level message specified by its key to the {@link org.springframework.binding.message.MessageContext}.
     *
     * @param messageContext The {@link org.springframework.binding.message.MessageContext} to which a resolved message must be added.
     * @param key            The key of the message in the FlowResourceBundle.
     */
    public static void addInfoFlowMessage(MessageContext messageContext, String key) {
        messageContext.addMessage(new MessageBuilder().info().code(
                key).build());
    }

    /**
     * Places all the {@link be.ganymed.gouse.exception.ApplicationException} onto the
     * {@link org.springframework.binding.message.MessageContext}.
     *
     * @param facesContext The {@link javax.faces.context.FacesContext} on which the messages must be placed.
     * @param e            The {@link be.ganymed.gouse.exception.ApplicationException} with its items.
     */
    public static void populateMessages(FacesContext facesContext, ApplicationException e) {
        for (ApplicationExceptionItem applicationExceptionItem : e.getApplicationExceptionItems()) {
            String message = MessageUtil.resolveFlowMessage(applicationExceptionItem.getMsgKey(), applicationExceptionItem.getMsgParams());
            facesContext.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, message, message));
        }
    }


    /**
     * Get a message from the application's resource files.
     *
     * @param key The key in the
     * @return
     */
    static public String getMessage(String key) {
        return getMessage(key, (Object[]) null);
    }

    /**
     * Get a formatted message from the application's resource files.
     *
     * @param key
     * @param args
     * @return
     */
    static public String getMessage(String key, String[] args) {
        return getMessage(key, (Object[]) args);
    }

    /**
     * Get a formatted message from the application's resource files.
     *
     * @param key
     * @param args
     * @return
     */
    static public String getMessage(String key, Object[] args) {
        if (key == null) {
            return key;
        }

        ResourceBundle bundle = ResourceBundle.getBundle(FacesContext
                .getCurrentInstance().getApplication().getMessageBundle(),
                getLocale());

        return getMessage(bundle, key, args);
    }

    /**
     * @param resourceBundleName
     * @param key
     * @return
     */
    public static String getMessage(String resourceBundleName, String key) {
        return getMessage(resourceBundleName, key, (Object[]) null);
    }

    /**
     * @param resourceBundleName
     * @param key
     * @param args
     * @return
     */
    public static String getMessage(String resourceBundleName, String key,
                                    String[] args) {
        return getMessage(resourceBundleName, key, (Object[]) args);
    }

    /**
     * @param resourceBundleName
     * @param key
     * @param args
     * @return
     */
    static public String getMessage(String resourceBundleName, String key,
                                    Object[] args) {
        if (key == null) {
            return key;
        }

        ResourceBundle bundle = FacesContext.getCurrentInstance()
                .getApplication().getResourceBundle(
                        FacesContext.getCurrentInstance(), resourceBundleName);

        return getMessage(bundle, key, args);
    }

    /**
     * @param bundle
     * @param key
     * @param args
     * @return
     */
    private static String getMessage(ResourceBundle bundle, String key,
                                     Object[] args) {
        String message = null;
        try {
            message = bundle.getString(key);
        } catch (MissingResourceException e) {
        }

        return getFormattedMessage((message != null) ? message : key, args);
    }

    /**
     * Format message using given arguments.
     *
     * @param message The string used as a pattern for inserting arguments.
     * @param args    The arguments to be inserted into the string.
     * @return
     */
    static public String getFormattedMessage(String message, Object[] args) {
        if ((args == null) || (args.length == 0)) {
            return message;
        }

        String result = null;
        try {
            MessageFormat mf = new MessageFormat(message);
            result = mf.format(args);
        } catch (NullPointerException e) {
        }

        return (result != null) ? result : message;
    }

    /**
     * Get locale from the FacesContext object.
     *
     * @return
     */
    protected static Locale getLocale() {
        FacesContext context = FacesContext.getCurrentInstance();
        if (context == null) {
            return Locale.getDefault();
        }

        // context.getViewRoot() may not have been initialized at this point.
        Locale locale = null;
        if (context.getViewRoot() != null) {
            locale = context.getViewRoot().getLocale();
        }

        return (locale != null) ? locale : Locale.getDefault();
    }

    /**
     * @param facesContext
     * @param component
     * @return
     */
    public static String getLabel(FacesContext facesContext,
                                  UIComponent component) {
        Object label = component.getAttributes().get("label");

        if (label == null
                || (label instanceof String && ((String) label).length() == 0)) {
            label = component.getValueExpression("label").getValue(
                    facesContext.getELContext());
        }

        if (label == null) {
            label = component.getId();
        }

        return label.toString();
    }
}